package com.example.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.*;

/**
 * @version 1.0.0
 * @description: redis配置
 * @author: guorf
 * @time: 2021/12/2 17:38
 */

@Slf4j
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisConfig {

    private final RedisProperty redisProperty;

    //RedisTemplate实例
    private static Map<Integer, RedisTemplate<String, Object>> redisTemplateMap = new HashMap<>();

    // 默认数据库
//    private int defaultDB;

    public RedisConfig(RedisProperty redisProperty) {
        this.redisProperty = redisProperty;
    }

    /**
     * @description: 初始化
     * @return: void
     * @author: guorf
     * @time: 2021/12/3 11:33
     */
    @PostConstruct
    public void initRedisTemplate() {
        if (null == redisProperty.getDatabaseList()) {
            List<Integer> databaseList = new ArrayList<>();
            databaseList.add(0);
            redisProperty.setDatabaseList(databaseList);
        }
        //设置默认数据库
//        defaultDB = redisProperty.getDatabaseList().get(0);
        for (Integer db : redisProperty.getDatabaseList()) {
            //存储多个RedisTemplate实例
            redisTemplateMap.put(db, redisTemplate(db));
        }
    }

    public LettuceConnectionFactory redisConnection(int db) {
        // 配置链接
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
        configuration.setHostName(redisProperty.getHost()); // 指定地址
        configuration.setDatabase(db); // 指定数据库
        configuration.setPort(redisProperty.getPort()); //指定端口
        configuration.setPassword(redisProperty.getPassword()); //指定密码
        // 配置连接池
        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(redisProperty.getMaxIdle());
        genericObjectPoolConfig.setMinIdle(redisProperty.getMinIdle());
        genericObjectPoolConfig.setMaxTotal(redisProperty.getMaxActive());
        genericObjectPoolConfig.setMaxWaitMillis(redisProperty.getMaxWait());
        genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(100);
        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(redisProperty.getTimeout()))
                .shutdownTimeout(Duration.ofMillis(redisProperty.getShutDownTimeout()))
                .poolConfig(genericObjectPoolConfig)
                .build();

        LettuceConnectionFactory factory = new LettuceConnectionFactory(configuration, clientConfig);
        factory.afterPropertiesSet(); //刷新配置
        return factory;
    }

    public RedisTemplate<String, Object> redisTemplate(int db) {
        //为了开发方便，一般直接使用<String,Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnection(db)); //设置连接
        //Json序列化配置
//        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = redisSerializer();
        FastJson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class);
        //String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        //key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value序列化方式采用jackson
        template.setValueSerializer(fastJsonRedisSerializer);
        //hash序列化方式采用jackson
        template.setHashValueSerializer(fastJsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

    /**
     * @description: 序列化
     * @param
     * @return: org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer
     * @author: guorf
     * @time: 2021/12/14 15:57
     */
    private Jackson2JsonRedisSerializer<Object> redisSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 指定序列化输入的类型，类必须是非final修饰的，final修饰的类，比如String,Integer等会跑出异常
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        //设置输入时忽略JSON字符串中存在而Java对象实际没有的属性
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        return jackson2JsonRedisSerializer;
    }

    /**
     * 指定数据库进行切换
     * @param db  数据库索引
     * @return
     */
    public RedisTemplate<String, Object> getRedisTemplateByDb(int db) {
        return redisTemplateMap.get(db);
    }

    /**
     * @description: 使用常量枚举进行指定数据库进行切换
     * @param redisContants
     * @return: org.springframework.data.redis.core.RedisTemplate<java.lang.String,java.lang.Object>
     * @author: guorf
     * @time: 2021/12/6 11:13
     */
    public RedisTemplate<String, Object> getRedisTemplateByDb(Map<String, Object> redisContants) {
        return redisTemplateMap.get(redisContants.get("db"));
    }


    /**
     * 使用默认数据库
     *
     * @return
     */
    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplateMap.get(redisProperty.getDatabase());
    }


    @Primary
    @Bean
    public CacheManager customCacheManager(){
       RedisTemplate<String, ?> redisTemplate = redisTemplateMap.get(redisProperty.getDatabase());
       RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(Objects.requireNonNull(redisTemplate.getConnectionFactory()));
//       Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = redisSerializer();
       FastJson2JsonRedisSerializer fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class);
       RedisSerializationContext<String, ?> serializationContext = RedisSerializationContext.fromSerializer(fastJsonRedisSerializer);
       RedisCacheConfiguration redisCacheConfiguration =  RedisCacheConfiguration.defaultCacheConfig().entryTtl(redisProperty.getTtl()).serializeValuesWith(serializationContext.getValueSerializationPair());
       return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }

}
